#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/io.h>
#include <string.h>
#include <stdlib.h> // malloc
#include <stdio_ext.h> // __fpurge

#include <sys/mman.h> // memoria
#include <errno.h> // errno

#include <sys/types.h> // wait
#include <sys/wait.h> // wait

#include <malloc.h> // memalign

void arquivo();
void io();
void memoria();
void processo();

/*
 * A manipulacao de erros sera desviada para esta funcao. Necessario
 * para observar o comportamento da funcao referente a memoria.
 */
static void handler(int sig, siginfo_t *si, void *unused)
{
	printf("SIGSEGV gerado no endereco: 0x%lx\n", (long) si->si_addr);
	exit(EXIT_FAILURE);
}

int main()
{
	int modo;

	printf("===================================================\n");
	printf("Escolha o modo:\n");
	printf(" 1: Gerenciamento de Arquivos\n 2: Entrada e Saida\n 3: Memoria\n 4: Processos\n");
	printf("===================================================\n");
	scanf("%d", &modo);

	while (modo != 0) {
		switch(modo) {
			case 1:
				printf("modo 1: Gerenciamento de Arquivos\n");
				arquivo();
				break;
			case 2:
				printf("modo 2: Entrada e Saida\n");
				io();
				break;
			case 3:
				printf("modo 3: Memoria\n");
				memoria();
				break;
			case 4:
				printf("modo 4: Processos\n");
				processo();
				break;
		}
		printf("===================================================\n");
		printf("Escolha o modo:\n");
		printf(" 1: Gerenciamento de Arquivos\n 2: Entrada e saida\n 3: Memoria\n 4: Processos\n");
		printf("===================================================\n");
		scanf("%d", &modo);
	}

	return 0;
}

/*
 * Funcao para teste das primitivas de Arquivos. Abre um arquivo no formato leitura e escrita
 * (caso o arquivo nao exista ele eh criado), escreve uma string pre-definida no arquivo
 * e depois o fecha.
 */
void arquivo() {
	int file;
	char *texto;

	/* Abre o arquivo teste.txt, se o arquivo nao existir ele eh criado */
	file = open("teste.txt", O_CREAT | O_RDWR | O_TRUNC, S_IRWXU);

	/* Verifica se o arquivo foi aberto com sucesso */
	if (file == -1)
		perror("Erro ao abrir o arquivo!\n");
	else
		printf("Arquivo aberto com sucesso!\n");

	texto = "Testing System Calls\n";

	/* Tenta escrever a string no arquivo */
	if (write(file, texto, strlen(texto)) == -1)
		printf("Erro na escrita!\n");
	else
		printf("Escreveu no arquivo\n");

	/* Fecha o arquivo */
	close(file);
}

/* Funcao para teste das primitivas de entrada e saida. Eh criado um array de chars
 * de tamanho 500 e lida uma frase da entrada (teclado) que depois sera impressa
 * setando as devidas permissoes para os dispositivos de I/O.
 */
void io()
{
	int count = 500;
	char array[500];

	printf("Digite uma frase:\n");
	__fpurge(stdin); /* limpa o buffer */
	count = read(0, array, count); /* le uma frase */

	/* Imprime o tamanho da frase */
	printf("Tamanho da frase digitada: %d\n", count - 1);

	printf("Voce digitou:\n");

	/* Seta as permissoes para os dispositivos de I/O */
	if (ioperm(0x300, 3, 1)) {
		write(1, array, count); /* E faz a escrita na saida */
	}

	/* Remove as permissoes */
	ioperm(0x300, 3, 0);
}

/*
 * Funcao para teste das primitivas de memoria. Primeiramente os sinais de erro
 * sao desviados para a funcao 'handler' depois eh alocada uma faixa de memoria sequencial
 * do tamanho de 4 paginas e entao o acesso eh bloqueado para a 3a e 4a pagina para fins
 * de teste.
 */
void memoria()
{
	char *buffer;
	int page_size, i;
	struct sigaction sa;

	/* Desvio do tratamento de erros */
	sa.sa_flags = SA_SIGINFO;
	sigemptyset(&sa.sa_mask);
	sa.sa_sigaction = handler;
	if (sigaction(SIGSEGV, &sa, NULL) == -1) {
		printf("Erro: sigaction\n");
		return;
	}
	
	page_size = sysconf(_SC_PAGE_SIZE);

	/* Alocacao da memoria alinhada */
	buffer = (char *) memalign(page_size, 4 * page_size);
	if (buffer == NULL) {
		printf("Erro: memalign\n");
		return;
	}

	/* Garante que a memoria alocada fique na RAM */
	mlock(buffer, 4 * page_size);

	printf("Inicio da regiao alocada: 0x%1x\n", (long) buffer);

	/* Bloqueia o acesso a 3a e 4a pagina */
	if (mprotect(buffer + page_size * 2, page_size, PROT_NONE) == -1) {
		printf("Erro: mprotect\n");
		return;
	}

	/* Tenta escrever nas paginas */
	for (i = 0; i < 4 * page_size; i++) {
		buffer[i] = 'a';
		i++;
	}

	/* libera a memoria (nunca chegara aqui) */
	munlock(buffer, 4 * page_size);
	free(buffer);
}

/*
 * Funcao para teste das primitivas de processos. Cria um processo filho que conta
 * ateh 5 e finaliza.
 */
void processo()
{
	pid_t pid;
	int status;

	pid = fork();

	/* Somente o processo filho executa */
	if (pid == 0) {
		int i;

		printf("Processo filho.\nContando:\n");
		for (i = 0; i < 5; i++) {
			printf("%d\n", i);
			sleep(1); /* dorme por 1 segundo */
		}
		printf("Processo filho terminou.\n");
		_exit(0); /* finaliza */
	}
	/* Somente o processo pai executa */
	else if (pid > 0) {
		printf("Processo pai aguardando...\n");
		wait(&status); /* aguarda o termino do filho */

		printf("Processo pai finalizado\n");
	}
	/* Caso ocorra erro no fork */
	else {
		printf("Erro: fork\n");
		exit(EXIT_FAILURE);
	}
}
